/* 2014/8/25 */
package com.cosmos.quartz.control;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;

import com.cosmos.quartz.launch.AgentLauncher;

/**
 * Provides mechanism of quartz control.
 * @author Leonardo
 *
 */
public class QuartzController {

	private static final Logger log = Logger.getLogger(QuartzController.class.getSimpleName());
	
	private static Scheduler scheduler;
	
	private static final Map<String, JobKey> jobMap;
	
	private static final Map<String, TriggerKey> triggerMap;
	
	private static final Map<String, String> jobTriggerMap;
	
	static {
		if (AgentLauncher.isLaunched()) {
			/*
			 * AgentLauncher is used as program entry.
			 */
			scheduler = AgentLauncher.getScheduler();
			jobMap = AgentLauncher.getJobMap();
			triggerMap = AgentLauncher.getTriggerMap();
			jobTriggerMap = AgentLauncher.getJobTriggerMap();
		} else {
			/*
			 * Program uses other entry class.
			 */
			scheduler = null;

			jobMap = new HashMap<>();
			triggerMap = new HashMap<>();
			jobTriggerMap = new HashMap<>();
			
			try {
				scheduler = StdSchedulerFactory.getDefaultScheduler();

				Set<JobKey> jobSet = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
				for (JobKey jobKey : jobSet) {
					jobMap.put(jobKey.getName(), jobKey);
				}
				
				Set<TriggerKey> triggerSet = scheduler.getTriggerKeys(GroupMatcher.anyTriggerGroup());
				for (TriggerKey triggerKey : triggerSet) {
					triggerMap.put(triggerKey.getName(), triggerKey);
				}
				
				for (JobKey jobKey : jobSet) {
					List<? extends Trigger> triggerList  = scheduler.getTriggersOfJob(jobKey);
					if (triggerList.size() > 0) {
						jobTriggerMap.put(jobKey.getName(), triggerList.get(0).getKey().getName());
					}
				}
			} catch (SchedulerException ex) {
				log.error(ex);
			}
		}
	}
	
	public ControlResponse process(ControlRequest request) {
		String jobName = request.getJobName();
		String operation = request.getOperation();
		boolean isSucceed = false;
		
		log.info(String.format("Request job name: %s, operation: %s.", jobName, operation));
		
		StringBuilder messageBuilder = new StringBuilder();
		StringBuilder reportBuilder = new StringBuilder();
		/*
		 * Invokes specified operation.
		 */
		if (operation == null || "".equals(operation)) {
			log.warn("No operation specified.");
			messageBuilder.append("No operation specified.");
		} else if (AgentControlService.OPERATION_PAUSE.equalsIgnoreCase(operation)) {
			isSucceed = pause(jobName, messageBuilder);
			report(jobName, reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_PAUSE_ALL.equalsIgnoreCase(operation)) {
			isSucceed = pauseAll(messageBuilder);
			reportAll(reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_RESUME.equalsIgnoreCase(operation)) {
			isSucceed = resume(jobName, messageBuilder);
			report(jobName, reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_RESUME_ALL.equalsIgnoreCase(operation)) {
			isSucceed = resumeAll(messageBuilder);
			reportAll(reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_REPORT.equalsIgnoreCase(operation)) {
			isSucceed = report(jobName, reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_REPORT_ALL.equalsIgnoreCase(operation)) { 
			isSucceed = reportAll(reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_TRIGGER.equalsIgnoreCase(operation)) {
			isSucceed = trigger(jobName, messageBuilder);
			report(jobName, reportBuilder, messageBuilder);
		} else if (AgentControlService.OPERATION_TRIGGER_All.equalsIgnoreCase(operation)) {
			isSucceed = triggerAll(messageBuilder);
			reportAll(reportBuilder, messageBuilder);
		} else {
			log.warn(String.format("Specified operation %s is not available.", operation));
			messageBuilder.append("Specified operation is not available.");
		}
		
		ControlResponse response = new ControlResponse();	
		response.setSucceed(isSucceed);
		response.setReport(reportBuilder.toString());
		response.setMessage(messageBuilder.toString());
		return response;
	}

	private boolean pause(String jobName, StringBuilder messageBuilder) {
		JobKey jobKey = getJobKey(jobName);
		if (jobKey == null) {				
			log.info(String.format("Cannot find specified job %s.", jobName));
			messageBuilder.append(String.format("Cannot find specified job %s.", jobName));
			return false;
		}
		
		try {
			if (isIgnoredJob(jobKey)) {
				log.info(String.format("Job %s is ignored job.", jobName));
				messageBuilder.append(String.format("Job %s is ignored job.", jobName));
				return false;
			}
			
			scheduler.pauseJob(jobKey);
			log.info(String.format("Pause job %s completed.", jobName));
		} catch (SchedulerException ex) {
			log.error(String.format("Pause job %s failed.", jobName), ex);
			messageBuilder.append(String.format("Pause job %s failed.", jobName));
		}
		return true;
	}

	private boolean pauseAll(StringBuilder messageBuilder) {		
		for (String jobName : jobMap.keySet()) {
			JobKey jobKey = getJobKey(jobName);
			if (jobKey != null) {
				try {
					if (!isIgnoredJob(jobKey)) {
						scheduler.pauseJob(jobKey);
					}
				} catch (Exception ex) {
					log.error(String.format("Pause job %s failed.", jobName), ex);
					messageBuilder.append(String.format("Pause job %s failed.", jobName));
				}
			}
		}
		return true;
	}
	
	private boolean resume(String jobName,	StringBuilder messageBuilder) {
		JobKey jobKey = getJobKey(jobName);		
		if (jobKey == null) {
			log.info(String.format("Cannot find specified job %s.", jobName));
			messageBuilder.append(String.format("Cannot find specified job %s.", jobName));
			return false;
		}
		
		try {
			if (isIgnoredJob(jobKey)) {
				log.info(String.format("Job %s is ignored job.", jobName));
				messageBuilder.append(String.format("Job %s is ignored job.", jobName));
				return false;
			}
			
			scheduler.resumeJob(jobKey);
			log.info(String.format("Resume job %s completed.", jobName));
		} catch (SchedulerException ex) {
			log.error(String.format("Resume job %s failed.", jobName), ex);
			messageBuilder.append(String.format("Resume job %s failed.", jobName));
		}
		return true;
	}

	private boolean resumeAll(StringBuilder messageBuilder) {		
		for (String jobName : jobMap.keySet()) {
			JobKey jobKey = getJobKey(jobName);
			if (jobKey != null) {
				try {
					if (!isIgnoredJob(jobKey)) {
						scheduler.resumeJob(jobKey);
					}
				} catch (Exception ex) {
					log.error(String.format("Resume job %s failed.", jobName), ex);
					messageBuilder.append(String.format("Resume job %s failed.", jobName));
				}
			}
		}
		return true;
	}

	private boolean report(String jobName, StringBuilder reportBuilder,
			StringBuilder messageBuilder) {
		String triggerName = getTriggerName(jobName);		
		TriggerKey triggerKey = getTriggerKey(triggerName);
		if (triggerKey == null) {
			log.info(String.format("Cannot find specified job %s.", jobName));
			messageBuilder.append(String.format("Cannot find specified job %s.", jobName));
			return false;
		}
		
		try {
			JobControlReport report = reportTrigger(jobName, triggerKey);
			log.info(String.format("Report job %s completed.", jobName));
			reportBuilder.append(report.toString());
		} catch (SchedulerException ex) {
			log.error(String.format("Report job %s failed.", jobName), ex);
			messageBuilder.append(String.format("Report job %s failed.", jobName));
		}
		return true;
	}

	private boolean reportAll(StringBuilder reportBuilder,
			StringBuilder messageBuilder) {
		JobControlReportList reportList = new JobControlReportList();
		for (String jobName : jobMap.keySet()) {
			String triggerName = getTriggerName(jobName);
			TriggerKey triggerKey = getTriggerKey(triggerName);
			if (triggerKey != null) {
				try {
					JobControlReport report = reportTrigger(jobName, triggerKey);
					reportList.add(report);
				} catch (SchedulerException ex) {
					log.error(String.format("Report job %s state failed.", jobName), ex);
					messageBuilder.append(String.format("Report job %s state failed.", jobName));
				}
			}
		}
		reportBuilder.append((reportList.toString()));
		return true;
	}
	
	private boolean trigger(String jobName, StringBuilder messageBuilder) {
		JobKey jobKey = getJobKey(jobName);
		if (jobKey == null) {
			log.info(String.format("Cannot find specified job %s.", jobName));
			messageBuilder.append(String.format("Cannot find specified job %s.", jobName));
			return false;
		}
		
		try {
			if (isIgnoredJob(jobKey)) {
				log.info(String.format("Job %s is ignored job.", jobName));
				messageBuilder.append(String.format("Job %s is ignored job.", jobName));
				return false;
			}
			
			scheduler.triggerJob(jobKey);
		} catch (SchedulerException ex) {
			log.error(String.format("Trigger job %s failed.", jobName), ex);
			messageBuilder.append(String.format("Trigger job %s failed.", jobName));
		}
		return true;
	}
	
	private boolean triggerAll(StringBuilder messageBuilder) {		
		for (String jobName : jobMap.keySet()) {
			JobKey jobKey = getJobKey(jobName);
			if (jobKey != null) {
				try {
					if (!isIgnoredJob(jobKey)) {
						scheduler.triggerJob(jobKey);
					}
				} catch (Exception ex) {
					log.error(String.format("Trigger job %s failed.", jobName), ex);
					messageBuilder.append(String.format("Trigger job %s failed.", jobName));
				}
			}
		}
		return true;
	}
	
	/**
	 * Retrieve JobKey from Quartz scheduler by specified job name.
	 */
	private JobKey getJobKey(String jobName) {
		JobKey jobKey = null;
		if (jobName == null || "".equals(jobName)) {
			log.warn("No job specified.");
			//messageBuilder.append("No job specified.");
		} else if (jobMap.containsKey(jobName)) {
			jobKey = jobMap.get(jobName);
		} else {
			log.warn(String.format("Specified job name: %s cannot be found.", jobName));
			//messageBuilder.append("Specified job name cannot be found.");
		}
		return jobKey;
	}
	
	private String getTriggerName(String jobName) {
		String triggerName = null;
		if (jobName == null || "".equals(jobName)) {
			log.warn("No job specified.");
			//messageBuilder.append("No job specified.");
		} else if (jobTriggerMap.containsKey(jobName)) {
			triggerName = jobTriggerMap.get(jobName);
		} else {
			log.warn(String.format("Specified job name: %s cannot be found.", jobName));
			//messageBuilder.append("Specified job name cannot be found.");
		}
		return triggerName;
	}
	
	private TriggerKey getTriggerKey(String triggerName) {
		TriggerKey triggerKey = null;
		if (triggerName == null || "".equals(triggerName)) {
			log.warn("No trigger specified.");
			//messageBuilder.append("No trigger specified.");
		} else if (triggerMap.containsKey(triggerName)) {
			triggerKey = triggerMap.get(triggerName);			
		} else {
			log.warn(String.format("Specified trigger name: %s cannot be found.", triggerName));
			//messageBuilder.append("Specified trigger name cannot be found.");
		}
		return triggerKey;
	}
	
	private JobControlReport reportTrigger(String jobName, TriggerKey triggerKey) throws SchedulerException {
		JobControlReport report = new JobControlReport();		
		report.setJobName(jobName);
		
		SimpleTrigger trigger = (SimpleTrigger)scheduler.getTrigger(triggerKey);
		report.setPreviousFireTime(trigger.getPreviousFireTime());
		report.setNextFireTime(trigger.getNextFireTime());
		report.setRepeatInterval(trigger.getRepeatInterval());
		report.setTriggerState(scheduler.getTriggerState(triggerKey));
		report.setCurrentTime(new Date());
		
		return report;
	}
	
	private boolean isIgnoredJob(JobKey jobKey) throws SchedulerException {
		JobDetail jobDetail = scheduler.getJobDetail(jobKey);
		Class<? extends Job> jobClass = jobDetail.getJobClass();
		return jobClass.isAnnotationPresent(AgentControlIgnore.class);
	}
	
}
